/*
 * xml_config.h
 *
 *  Created on: 2008-10-6
 *      Author: fanliangliang
 */

#ifndef XML_CONFIG_H_
#define XML_CONFIG_H_

#include "toxic.h"
#include <libxml/xpath.h>
#include <string>
#include <vector>
#include <cstdarg>

using std::string;
using std::vector;

class XmlConfig {
public:
	explicit XmlConfig(const string& pathname);
	XmlConfig();
	~XmlConfig();

public:
	bool load(const string& pathname);

	void setExceptionEnabled(bool enable);
	bool exceptionEnabled();

	void setRoot(const string& xpath);
	const string& root() const;

	operator void*() const;
	bool operator !() const;

	const string& pathname() const;

	size_t count(const string& xpath);
	size_t countf(const char* format, ...);

	template <typename Container>
	bool read(Container& result, const string& xpath,
		const typename Container::value_type& defaultValue = typename Container::value_type());

	template <typename Container>
	bool readf(Container& result, const char* format, ...);

	template <typename T>
	T read(const string& xpath, const T& defaultValue = T());

	template <typename T>
	T readf(const char* format, ...);

private:
	void throwXmlException(const string& what);
	void setLastPath(const string& xpath);
	const string& lastPath() const;
	xmlXPathObjectPtr evaluate(const string& xpath);

	size_t countImplement(const string& xpath);

	bool readStringImplement(string& result, const string& xpath);
	template <typename Container>
	bool readStringListImplement(Container& result, const string& xpath,
		const typename Container::value_type& defaultValue);

	bool readString(string& result, const string& xpath);
	template <typename Container>
	bool readStringList(Container& result, const string& xpath,
		const typename Container::value_type& defaultValue);

	void vcountf(size_t& value, const char* format, va_list alist);

	template <typename T>
	void vreadf(T& value, const char* format, va_list alist);

	template <typename Container>
	bool vreadfc(Container& result, const char* format, va_list alist);

private:
	xmlDocPtr _doc;
	string _pathname;
	string _root;
	string _lastPath;
	bool _exceptionEnabled;
};


const char* codeConvert(const char* in, const char* fromCode, const char* toCode);
void codeConvert(string* out, const string& in, const string& fromCode, const string& toCode);

template <typename Container>
inline bool XmlConfig::readStringListImplement(Container& result, const string& xpath,
	const typename Container::value_type& defaultValue /*= typename Container::value_type()*/) {

	xmlXPathObjectPtr object = evaluate(xpath);
	if (object == 0) {
		return false;
	}

	xmlNodeSetPtr nodeSet = object->nodesetval;
	if (xmlXPathNodeSetIsEmpty(nodeSet)) {
		xmlXPathFreeObject(object);
		return false;
	}

	for (int i = 0; i < nodeSet->nodeNr; ++i) {
		xmlChar* value = xmlNodeListGetString(_doc, nodeSet->nodeTab[i]->children, 1);

		const char* cstr = codeConvert((const char*)value, "utf-8", (const char*)_doc->encoding);

		xmlFree(value);

		if (cstr == 0) {
			xmlXPathFreeObject(object);
			return false;
		}

		try {
			toxic::insert(result, string(cstr));
		}
		catch (const toxic::bad_lexical_cast& e) {
			// TODO
			toxic::insert(result, defaultValue);
		}

		delete [] cstr;
	}

	xmlXPathFreeObject(object);
	return true;
}

template <typename Container>
inline bool XmlConfig::readStringList(Container& result, const string& xpath,
	const typename Container::value_type& defaultValue) {

	bool succeed = readStringListImplement(result, xpath, defaultValue);
	if (!succeed) {
		throwXmlException("Failed to evaluate XPath expression:" + lastPath());
	}
	return succeed;
}

template <typename Container>
inline bool XmlConfig::read(Container& result, const string& xpath,
	const typename Container::value_type& defaultValue) {

	return readStringList(result, xpath, defaultValue);
}

template <typename T>
inline T XmlConfig::read(const string& xpath, const T& defaultValue) {
	string str;
	if (!readString(str, xpath)) {
		return defaultValue;
	}

	T result(defaultValue);
	try {
		result = toxic::lexical_cast<T>(str);
	}
	catch (const toxic::bad_lexical_cast& e) {
		// TODO
	}
	return result;
}

template <>
inline string XmlConfig::read<string>(const string& xpath, const string& defaultValue /*= T()*/) {
	string result;
	if (!readString(result, xpath)) {
		return defaultValue;
	}
	return result;
}

template <typename T>
inline T XmlConfig::readf(const char* format, ...) {
	T result;
	va_list alist;
  	va_start(alist, format);
  	vreadf(result, format, alist);
	va_end(alist);
	return result;
}

template <typename T>
inline void XmlConfig::vreadf(T& value, const char* format, va_list alist) {
	char xpath[512] = { 0 };
	vsnprintf(xpath, sizeof(xpath), format, alist);
	value = read<T>(xpath);
}

template <typename Container>
inline bool XmlConfig::readf(Container& result, const char* format, ...) {
	va_list alist;
  	va_start(alist, format);
  	bool isOk = vreadfc(result, format, alist);
	va_end(alist);
	return isOk;
}

template <typename Container>
inline bool XmlConfig::vreadfc(Container& result, const char* format, va_list alist) {
	char xpath[512] = { 0 };
	vsnprintf(xpath, sizeof(xpath), format, alist);
	return read(result, xpath);
}

#endif /* XML_CONFIG_H_ */
